package java核心及工具类;

负数的二进制表示方法:负数以其正值的补码形式表达

假设有一个 int 类型的数，值为3，那么，我们知道它在计算机中表示为：

00000000 00000000 00000000 00000011

因为int类型的数占用4字节（32位），所以前面填了一堆0。在计算机中，负数以其正值的补码形式表达。什么叫补码呢？这得先从原码，反码说起。

原码：一个整数，按照绝对值大小转换成的二进制数，称为原码。比如 00000000 00000000 00000000 00000011 是3的原码。

反码：将二进制数按位取反，所得的新二进制数称为原二进制数的反码。取反操作指：1变0；0变1
比如:00000000 00000000 00000000 00000011的反码是:
    11111111 11111111 11111111 11111100。　

补码：反码加1称为补码。也就是说，要得到一个数的补码，先得到反码，然后将反码加上1，所得数称为补码。

比如：00000000 00000000 00000000 00000011 的反码是：     11111111 11111111 11111111 11111100。
那么，补码为: 11111111 11111111 11111111 11111100 + 1 = 11111111 11111111 11111111 11111101

所以，-3 在计算机中表达为：11111111 11111111 11111111 11111101。转换为十六进制：0xFFFFFFFD。

整数-1在计算机中如何表示。假设这也是一个int类型，那么：

1、先取1的原码： 00000000 00000000 00000000 00000001
2、得反码：     11111111 11111111 11111111 11111110
3、得补码：     11111111 11111111 11111111 11111111
可见，－1在计算机里用二进制表达就是全1。16进制为：0xFFFFFF

负数用补码原因：

INT_MAX = 2147483647 ;

cout<<INT_MAX+1<<endl; //正确结果为-2147483648

UINT_MAX = 4294967295；

cout<<UINT_MAX+1<<endl; //正确结果为0

负数在计算机中如何表示？

举例来说，+8在计算机中表示为二进制的1000，那么-8怎么表示呢？

很容易想到，可以将一个二进制位（bit）专门规定为符号位，它等于0时就表示正数，等于1时就表示负数。比如，在8位机中，规定每个字节的最高位为
//符号位。那么，+8就是00001000，而-8则是10001000。

但是，随便找一本《计算机原理》，都会告诉你，实际上，计算机内部采用2的补码（Two's Complement）表示负数。

什么是2的补码？

它是一种数值的转换方法，要分二步完成：

第一步，每一个二进制位都取相反值，0变成1，1变成0。比如，0000 1000的相反值就是11110111。

第二步，将上一步得到的值加1。11110111就变成11111000。

所以，00001000的2的补码就是1111 1000。也就是说，-8在计算机（8位机）中就是用1111 1000表示。

不知道你怎么看，反正我觉得很奇怪，为什么要采用这么麻烦的方式表示负数，更直觉的方式难道不好吗？

为什么要用2的补码

首先，要明确一点。计算机内部用什么方式表示负数，其实是无所谓的。只要能够保持一一对应的关系，就可以用任意方式表示负数。所以，既然可以任意选择，那么理应选择一种最方便的方式。

2的补码就是最方便的方式。它的便利体现在，所有的加法运算可以使用同一种电路完成。

还是以-8作为例子。

假定有两种表示方法。一种是直觉表示法，即10001000；另一种是2的补码表示法，即1 1111000。请问哪一种表示法在加法运算中更方便？

随便写一个计算式，16 + (-8) = ?                     1 0001000 取反 1 1110111    加1    +1 = 1 1111000   

再取反  1 0000111  + 1  = 1 0001000 取反不包括符号位两次取反得到原值                                                                         

正数的补码是其本身 负数的补码是符号位不变 其他位取反之后加1  

连着变换两次相当于没有做任何操作
16的二进制表示是 00010000，所以用直觉表示法，加法就要写成：                                                        

　０００１００００                                                                                                                                  
＋１０００１０００
－－－－－－－－－
　１００１１０００

可以看到，如果按照正常的加法规则，就会得到10011000的结果，转成十进制就是-24。显然，这是错误的答案。也就是说，在这种情况下，正常的加法规则不适用于正
数与负数的加法，因此必须制定两套运算规则，一套用于正数加正数，还有一套用于正数加负数。从电路上说，就是必须为加法运算做两种电路。

现在，再来看2的补码表示法。

　０００１００００
＋１１１１１０００
－－－－－－－－－
１００００１０００

可以看到，按照正常的加法规则，得到的结果是100001000。注意，这是一个9位的二进制数。我们已经假定这是一台8位机，因此最高的第9位是一个溢出位，会被自动
舍去。所以，结果就变成了00001000，转成十进制正好是8，也就是16 + (-8) 的正确答案。这说明了，2的补码表示法可以将加法运算规则，扩展到整个整数集，从
而用一套电路就可以实现全部整数的加法。

2的补码的本质及正确性

我们要看先一下模的概念

“模”是指一个计量系统的计数范围。如时钟等。计算机也可以看成一个计量机器，它也有一个计量范围，即都存在一个“模”。例如：

　　时钟的计量范围是0～11，模=12。

　　表示n位的计算机计量范围是0～2^(n)-1，模=2^(n)。

　　“模”实质上是计量器产生“溢出”的量，它的值在计量器上表示不出来，计量器上只能表示出模的余数。任何有模的计量器，均可化减法为加法运算。

　　例如： 假设当前时针指向10点，而准确时间是6点，调整时间可有以下两种拨法：

　　①你可以往回拨4个小时，②也可以向前拨8个小时（12-10+6，在钟表系统里模是12）

　　在以12模的系统中，加8和减4效果是一样的，因此凡是减4运算，都可以用加8来代替。

　　对“模”而言，8和4互为补数。实际上以12模的系统中，11和1，10和2，9和3，7和5，6和6都有这个特性。共同的特点是两者相加等于模。

　　对于计算机，其概念和方法完全一样。n位计算机，设n=8， 所能表示的最大数是11111111，若再加1称为100000000(9位)，但因只有8位，
   最高位1自然丢失。又回了00000000，所以8位二进制系统的模为2^8。 在这样的系统中减法问题也可以化成加法问题，只需把减数用相应的补数表示就可以了。

       再次重申一下这句话：

      在以12模的系统中，加8和减4效果是一样的，因此凡是减4运算，都可以用加8来代替。      

      所以对于模为10000 0000的8位系统来说，减去b和加上10000 0000-b是一个道理，而（10000 0000-b）是什么？恰好就是b的补码

补码怎么求，“取反加一” 这口诀怎么来的？

承认了8 - 5 = 8 + （-5的补码）这个事实后，我们来看-5的补码怎么求，“取反加一”怎么来的

其实看完了上面的模的问题，该问题的答案基本已经出来了

-5的补码是 10000 0000 - 5 = 1111 1111 + 1 -5 = （1111 1111 - 5） + 1

1111 1111减去一个数事实上就是在对这个数取反，后面那个是+1

两个小问题的解释：

(1)

32位系统里，int的最大值为01111111 11111111 11111111 11111111，加1之后为

10000000   00000000   00000000   00000000。这个数是什么？

首先这是个负数-->负数在计算器里都是补码形式存放-->这是个补码-->那么真值是？--> -2147483648（已知负数的补码求该负数，不会求的百度一下吧。。。）

(2)                                                                                                                                                                     再取反就得到原来的值
对于unsigned，最大值（32个1）加1后最前面的1自然丢失，剩下32个0，所以就是0。



《八位二进制数能表示数的范围以及原码、反码和补码含义》

首先八位二进制数0000 0000 ~1111 1111，一共可以表示2^8=256位数，如果表示无符号整数可以表示0~255。计算方法就是二进制与十进制之间的转换。

如果想要表示有符号整数，就要将最前面一个二进制位作为符号位，即0代表正数，1代表负数，后面7位为数值域，这就是原码定义。这样在现实生活中完全没有问题，
但在计算机中就出现了问题。

数的表示：
在原码中，0的表示有两种（+0）0000 0000、（-0）1000 0000，这样就产生了编码映射的不唯一性，在计算机上就要区分辨别。然而+0、-0却没有什么现实意义。

数的运算：
为了解决上述数的表示问题，我们可以强制把转换后的10000000强制认定为-128。但这又出现了一个新的问题就是数的运算。数学上，1+(-1)=0，而
在二进制中00000001+10000001=10000010，换算成十进制为-2。显然出错了。所以原码的符号位不能直接参与运算，必须和其他位分开，这就增加
了硬件的开销和复杂性。

这个时候就要引入补码，补码表示法规定：正数的补码与其原码相同；负数的补码是在其反码的末位加1。反码定义为：正数的反码与其原码相同；负数的反码是对其原码逐
位取反，但符号位除外。

1、但为什么要引入补码呢？

2、以及负数补码定义为什么是相对应的正数原码取反加一？

先解决第一个问题，引入补码是为了解决计算机中数的表示和数的运算问题，使用补码，可以将符号位和数值域统一处理，即引用了模运算在数理上对符号位的自动处理，利
用模的自动丢弃实现了符号位的自然处理，仅仅通过编码的改变就可以在不更改机器物理架构的基础上完成的预期的要求。

要解决第二个问题同时理解补码，首先要理解“模”。

模的概念可以帮助理解补数和补码。

“模”是指一个计量系统的计数范围。如时钟等。计算机也可以看成一个计量机器，它也有一个计量范围，即都存在一个“模”。例如：

时钟的计量范围是0～11，模=12。表示n位的计算机计量范围是0～2^(n)-1，模=2^(n)。

“模”实质上是计量器产生“溢出”的量，它的值在计量器上表示不出来，计量器上只能表示出模的余数。任何有模的计量器，均可化减法为加法运算。

例如：假设当前时针指向10点，而准确时间是6点，调整时间可有以下两种拨法：一种是倒拨4小时，即：10-4=6；另一种是顺拨8小时：10+8=12+6=6

在以12模的系统中，加8和减4效果是一样的，因此凡是减4运算，都可以用加8来代替。对“模”而言，8和4互为补数。实际上以12模的系统中，
11和1，10和2，9和3，7和5，6和6都有这个特性。共同的特点是两者相加等于模。

对于计算机，其概念和方法完全一样。n位计算机，设n=8,不讨论符号位，则所能表示的最大数是1111 1111，若再加1成为1 0000 0000(9位)，但因只
有8位，最高位1自然丢失。又回了00000000，所以8位二进制系统的模为2^9。在这样的系统中减法问题也可以化成加法问题，只需把减数用相应的补数表示就
可以了。把补数用到计算机对数的处理上，就是补码。

接下来就是取反加一的来源：

例如-8，可以看做0-8，即0000 0000 - 0000 1000，小减大，像高位借一，即1 0000 0000 - 0000 1000 = 1111 1000，这个可以看做用模数来
减去该数，会得到该数的补数。同时上式也可以看做:
  (1 0000 0000-0000 1000)->(1111 1111+1)-0000 1000->(1111 1111-0000 1000)+1->(1111 0111)+1，即0000 1000取反加1。

负数补码定义是相对应的正数原码取反加一，这个同负数的反码末尾加一是同样的效果。注意看反码的定义。取反加一是逐位取反，包括符号位。
                         6的反码: 0000 0110->0111 1001    -7的补码: 1000 0111->0111 1001
对一个正数的原码取反加一，得到这个正数对应负数的补码。例如~6=-7，而且加一之后会多出一个八进制补码1000 0000，而这个补码就对应着原码1000 0000，
数字位同时当做符号位即-128。

根据以上内容我们就可以来解释八位二进制数的表示范围

八位二进制正数的补码范围是0000 0000 ~ 0111 1111 即0 ~ 127,
负数的补码范围是正数的原码0000 0000 ~ 0111 1111 取反加一（也可以理解为负数1000 0000 ~ 1111 1111化为反码末尾再加一）。

所以得到 1 0000 0000 ~ 1000 0001,1000 0001作为补码，其原码是1111 1111（-127），依次往前推，可得到-1的补码为1111 1111，
那么补码0000 0000的原码是1000 0000符号位同时也可以看做数字位即表示-128，这也解释了为什么127（0111 1111）+1（0000 0001）=-128（1000 0000）。

在计算机中数据用补码表示，利用补码统一了符号位与数值位的运算，同时解决了+0、-0问题，将空出来的二进制原码1000 0000表示为-128，这也符
合自身逻辑意义的完整性。因此八位二进制数表示范围为-128~+127。


《机器数、真值、原码、反码和补码》 文中所有二进制为了表示方便皆采用1字节来表示

一. 机器数、真值
1. 机器数
机器数就是一个数在计算机中的二进制表示，计算机中机器数的最高位是符号位，正数符号位为0，负数符号位为1。机器数包含原码、反码和补码三种表示形式。

例如：

十进制中的+2在计算机中的字长为8，转换成二进制为 0000 0010
十进制中的-2在计算机中的字长为8，转换成二进制为 1000 0010
上文例子中的0000 0010和1000 0010就是机器数

2. 机器数的真值
真值就是带符号位的机器数对应的真正数值，个人理解就是用正负号来代替符号位来表示机器数 例如:

机器数 0000 0010 的真值为 +000 0010 也就是+2
机器数 1000 0010 的真值为 -000 0010 也就是-2
二. 机器数的三种表示形式：原码、反码、补码
1. 原码
原码就是加了一位符号位的二进制数，正数符号位为0，负数符号位为1，符号位为最高位。 个人理解就是将真值里面的"+"转换为0，"-"转换为1。

| 十进制 | 　　真值    | 　　原码    | 
| ＋2   | +000 0010 | 0000 0010 | 
| －2   | -000 0010 | 1000 0010 |

由此可以得出8位的二进制数的大小范围为[1111 1111, 0111 1111]，也就是[-127, 127]对应java中byte数据类型的大小范围

2. 反码
正数的反码就是其原码，负数的反码则是符号位不变，其他位取反（0变1，1变0）

| 十进制 | 　　真值    | 　　原码    | 
| ＋2　　| 0000 0010 | 0000 0010 | 
| －2　　| 1000 0010 | 1111 1101 |

3. 补码
正数的补码就是其原码，负数的补码则是反码+1

| 十进制 | 　　真值    | 　　原码    |    补码    |
| ＋2　　| 0000 0010 | 0000 0010 | 0000 0010 |
| －2　　| 1000 0010 | 1111 1101 | 1111 1110 |

4. 为何使用反码、补码
使用原码进行计算的时候，对于人而言能够很轻易的辨别出符号位，然后直接对其他位数值进行计算。然而对于计算机的设计而言辨别出符号位就是一项非常
复杂的工程，所以设计的时候就考虑让符号位直接参与计算，这样设计计算机就十分简单了。 对于加法而言符号位对于计算并没有影响，对于减法而言则考
虑通过加上负数来转换为加法的方式进行计算。 如果通过原码来直接进行减法计算：

3 - 2 
= 3 + (-2)
= 0000 0011(原) + 1000 0010(原)
= 1000 0101
= -5
结果显而易见，如果通过原码来直接让符号位参与运算的话是不正确的，所以为了解决减法的问题引入了反码的概念。如果通过反码来进行减法计算：

3 - 2
 = 3 + (-2)   
 = 0000 0011(原) + 1000 0010(原)
 = 0000 0011(反) + 1111 1101(反)
 = 1 0000 0000(反) -- 最高位产生进位，结果+1
 = 0000 0001(反)
 = 0000 0001(原)
 = 1
结果正确，从上面例子看来如果通过反码进行减法运算的话是没有问题的，那为什么又需要补码呢，我们一起来看下面这个特殊的例子：

2 - 2
= 2 + (-2)
= 0000 0010(原) + 1000 0010(原)
= 0000 0010(反) + 1111 1101(反)
= 1111 1111(反)
= 1000 0000(原)
= -0

  0 + 0
= 0000 0000(原) + 0000 0000(原)
= 0000 0000(反) + 0000 0000(反)
= 0000 0000(反)
= 0000 0000(原)
= 0
由于对于0这个数字而言，正负号没有任何意义，但是经过计算却有可能出现[0000 0000]和[1000 0000]这两种不同的原码表示同一个数字0，这显然是
不合理的，所以此时就引入了补码的概念。 如果通过补码来进行上述例子的计算：

2 - 2
= 2 + (-2)
= 0000 0010(原) + 1000 0010(原)
= 0000 0010(反) + 1111 1101(反)
= 0000 0010(补) + 1111 1110(补)
= 1 0000 0000(补) -- 最高位产生进位，进位舍弃
= 0000 0000(补)
= 0000 0000(反)
= 0000 0000(原)
= 0

  0 + 0
= 0000 0000(原) + 0000 0000(原)
= 0000 0000(反) + 0000 0000(反)
= 0000 0000(补) + 0000 0000(补)
= 0000 0000(反)
= 0000 0000(原)
= 0
由上述例子可以看出，补码完美的解决了0的符号问题以及0有两个不同原码表示的问题。而且[1 0000 0000]也可以用来表示-128：

-1 - 127
= -1 + (-127)
= 1000 0001(原) + 1111 1111(原)
= 1111 1110(反) + 1000 0000(反)
= 1111 1111(补) + 1000 0001(补)
= 1 1000 0000(补) --最高位产生进位，进位舍弃
= 1000 0000(补)
-1 - 127的结果为-128，上面例子中-1和-127补码相加后得出的补码也是-128。但是这个1000 0000(补)实际上对应的是之前的-0，所以这个补码
是没有反码和原码的。 综上可以看出使用补码的话不仅0的符号问题和多原码问题可以解决，还可以多表示一个最小数。因此对于1字节而言，原码和反码的
范围是[-127, 127]，而补码的范围是[-128, 127]，也可以解释java中int的范围是[-2, 2-1]。
